/*
 * graph2D
 * Copyright (c) 2011 Shun Moriya <shun@mnu.sakura.ne.jp>
 *
 * The MIT License
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <libkern/OSByteOrder.h>
#include <fstream>
#include "PVRTCLoader.h"

using namespace std;

namespace Graph2D
{
	namespace Implementation
	{
		const char PVRTCLoader::PVRIdentifier[4] = { 'P', 'V', 'R', '!' };

		PVRTCLoader::PVRTCLoader()
			: name(0)
			, numSurfaces(0)
		{
		}

		PVRTCLoader::PVRTCLoader(const char* path)
			: name(0)
			, numSurfaces(0)
		{
			loadFromFile(path);
		}

		PVRTCLoader::~PVRTCLoader()
		{
			glDeleteTextures(1, &name);
		}

		bool PVRTCLoader::loadFromFile(const char* path)
		{
			char* readBuffer = NULL;

			try
			{
				std::ifstream file;
				file.open(path);
				if(!file.good())
					return false;

				file.seekg(0, ios::end);
				int size = file.tellg();
				file.seekg(0, ios::beg);

				readBuffer = new char[size];
				file.read(readBuffer, size);

				bool result = loadFromMemory(readBuffer);

				delete[] readBuffer;

				return result;
			}
			catch(...)
			{
				delete[] readBuffer;

				throw;
			}
		}

		bool PVRTCLoader::loadFromMemory(const void* pData)
		{
			const Header& header = *reinterpret_cast<const Header *>(pData);
			unsigned int tag = OSSwapLittleToHostInt32(header.tag);

			if(
				PVRIdentifier[0] != ((tag >>  0) & 0xff) ||
				PVRIdentifier[1] != ((tag >>  8) & 0xff) ||
				PVRIdentifier[2] != ((tag >> 16) & 0xff) ||
				PVRIdentifier[3] != ((tag >> 24) & 0xff) )
			{
				return false;
			}

			if(name)
			{
				glDeleteTextures(1, &name);
				name = 0;
			}

			GLuint flags       = OSSwapLittleToHostInt32(header.flags);
			GLuint formatFlags = flags & PVRTC_FLAG_TYPE_MASK;
			GLenum type        = 0;
			switch(formatFlags)
			{
			case PVRTC_FLAG_TYPE_RGBA4444: format = GL_RGBA4; type = GL_UNSIGNED_SHORT_4_4_4_4; break;
			case PVRTC_FLAG_TYPE_RGBA1555: format = GL_RGB5_A1; type = GL_UNSIGNED_SHORT_5_5_5_1; break;
			case PVRTC_FLAG_TYPE_RGBA8888: format = GL_RGBA8; type = GL_UNSIGNED_BYTE; break;
			case PVRTC_FLAG_TYPE_RGB565:   format = GL_RGB565; type = GL_UNSIGNED_SHORT_5_6_5; break;
			case PVRTC_FLAG_TYPE_RGB888:   format = GL_RGB8; type = GL_UNSIGNED_BYTE; break;
			case PVRTC_FLAG_TYPE_I8:       format = GL_LUMINANCE8; type = GL_UNSIGNED_BYTE; break;
			case PVRTC_FLAG_TYPE_AI88:     format = GL_LUMINANCE8_ALPHA8; type = GL_UNSIGNED_BYTE; break;
			case PVRTC_FLAG_TYPE_PVRTC_2:  format = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; break;
			case PVRTC_FLAG_TYPE_PVRTC_4:  format = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break;
			case PVRTC_FLAG_TYPE_A8:       format = GL_ALPHA8; type = GL_UNSIGNED_BYTE; break;
			default: return false;
			}

			width       = OSSwapLittleToHostInt32(header.width);
			height      = OSSwapLittleToHostInt32(header.height);
			alpha       = OSSwapLittleToHostInt32(header.bitmaskAlpha) ? true : false;
			numSurfaces = 0;

			GLuint w      = width;
			GLuint h      = height;
			GLuint offset = 0;
			GLuint size   = OSSwapLittleToHostInt32(header.dataSize);
			GLuint bpp    = OSSwapLittleToHostInt32(header.bpp);
			const uint8_t* pBytes = reinterpret_cast<const uint8_t*>(pData) + sizeof(header);

			while(offset < size && numSurfaces < PVRTC_MAX_SURFACES)
			{
				GLuint blockSize, widthBlocks, heightBlocks;
				Surface& surface = surfaces[numSurfaces++];

				switch(formatFlags)
				{
				case PVRTC_FLAG_TYPE_RGBA4444:
				case PVRTC_FLAG_TYPE_RGBA1555:
				case PVRTC_FLAG_TYPE_RGBA8888:
				case PVRTC_FLAG_TYPE_RGB565:
				case PVRTC_FLAG_TYPE_RGB888:
				case PVRTC_FLAG_TYPE_I8:
				case PVRTC_FLAG_TYPE_AI88:
				case PVRTC_FLAG_TYPE_A8:
					blockSize = 1;
					widthBlocks = w;
					heightBlocks = h;
					break;
				case PVRTC_FLAG_TYPE_PVRTC_2:
					blockSize    = 8 * 4;
					widthBlocks  = w / 8;
					heightBlocks = h / 4;
					break;
				case PVRTC_FLAG_TYPE_PVRTC_4:
					blockSize    = 4 * 4;
					widthBlocks  = w / 4;
					heightBlocks = h / 4;
					break;
				}

				if(widthBlocks < 2)
					widthBlocks = 2;
				if(heightBlocks < 2)
					heightBlocks = 2;

				surface.size = widthBlocks * heightBlocks * ((blockSize * bpp) / 8);
				surface.bits = &pBytes[offset];
				w >>= 1;
				if(w <= 0)
					w = 1;
				h >>= 1;
				if(h <= 0)
					h = 1;
				offset += surface.size;
			}

			glGenTextures(1, &name);
			glBindTexture(GL_TEXTURE_2D, name);

			w = width;
			h = height;

			for(GLuint i = 0 ; i < numSurfaces; ++i)
			{
				const Surface& surface = surfaces[i];

				if(type)
				{
					glTexImage2D(
						GL_TEXTURE_2D,
						i,
						format,
						w,
						h,
						0,
						format,
						surface.size,
						surface.bits
					);
				}
				else
				{
					glCompressedTexImage2D(
						GL_TEXTURE_2D,
						i,
						format,
						w,
						h,
						0,
						surface.size,
						surface.bits
					);
				}
				w >>= 1;
				if(w <= 0)
					w = 1;
				h >>= 1;
				if(h <= 0)
					h = 1;
			}

			return true;
		}
	}
}
